/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package us.levk.tools.collections;

import java.util.AbstractMap;
import java.util.Map;
import java.util.Set;


/**
 * <p>Map that constructs new values for unbound keys. This implementation breaks strict Map interface!</p>
 * 
 * <p>This is a product of a common pattern. Often I would want to use a map for data driven code paths
 * and if I do not have a previously defined path for a particular spot I always have to do the same thing
 * to either make a new path or follow the same exact path, this simply factors this code</p> 
 *
 * @author levk
 *
 */
public class DefaultingMap<K, V> extends AbstractMap<K, V> {

  /**
   * Constructs a new value given the key as the seed
   * 
   * @author levk
   *
   * @param <K>
   * @param <V>
   */
  public interface Factory<K, V> {

    public V make (K seed);
  }

  /**
   * Wrapped map
   */
  private final Map<K, V> wrapped;
  /**
   * Value factory for unbound keys
   */
  private final Factory<K, V> factory;

  /**
   * Wraps the specified map in the defaulting wrapper, this wrapper will not auto insert newly created
   * bindings
   * 
   * @param wrapped
   * @param factory
   */
  public DefaultingMap (Map<K, V> wrapped, final Factory<K, V> factory) { this (wrapped, factory, false); }

  /**
   * Wraps the specified map in the defaulting wrapper
   * 
   * @param wrapped
   * @param factory
   * @param autoInsert whether to automatically insert the new binding into the wrapped map
   */
  public DefaultingMap (Map<K, V> wrapped, final Factory<K, V> factory, boolean autoInsert) {
    this.wrapped = wrapped;
    this.factory = autoInsert
        ? new Factory<K, V> () {
          public V make (K seed) {
            V result = factory.make (seed);
            DefaultingMap.this.put (seed, result);
            return result;
          }
        } 
        : factory;
  }

  /* (non-Javadoc)
   * @see java.util.AbstractMap#get(java.lang.Object)
   */
  @SuppressWarnings ("unchecked") @Override public V get (Object key) { return wrapped.containsKey (key) ? wrapped.get (key) : factory.make ((K) key); }

  /* (non-Javadoc)
   * @see java.util.AbstractMap#put(java.lang.Object, java.lang.Object)
   */
  @Override public V put (K key, V value) { return wrapped.put (key, value); }

  /* (non-Javadoc)
   * @see java.util.AbstractMap#entrySet()
   */
  @Override public Set<java.util.Map.Entry<K, V>> entrySet () { return wrapped.entrySet (); }
}
